package com.xd.train.common.aspect;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

/**
 * AOP 日志切面类：拦截 Controller 接口请求，打印请求信息、返回结果及耗时
 */
@Aspect
@Component
public class LogAspect {
    // 构造方法：初始化时打印提示（验证切面是否被Spring加载）
    public LogAspect() {
        System.out.println("Common LogAspect");
    }

    // 日志对象：用于输出日志信息
    private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);

    /**
     * 定义切点：拦截 com.hk 包下所有 Controller 类的所有公共方法
     * 匹配规则：execution(public * com.hk..*Controller.*(..))
     * - public *：公共方法，返回值任意
     * - com.hk..：com.hk 包及其子包
     * - *Controller：类名以 Controller 结尾
     * - *(..)：方法名任意，参数任意
     */
    @Pointcut("execution(public * com.xd..*Controller.*(..))")
    public void controllerPointcut() {
    }

    /**
     * 前置通知：在 Controller 方法执行前执行，打印请求基本信息
     * @param joinPoint 连接点：包含当前执行方法的信息（如方法名、参数等）
     */
    @Before("controllerPointcut()")
    public void doBefore(JoinPoint joinPoint) {
        // 1. 获取请求上下文信息（如请求地址、请求方法等）
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取当前执行方法的签名（包含类名、方法名）
        Signature signature = joinPoint.getSignature();
        String methodName = signature.getName();
        String className = signature.getDeclaringTypeName();

        // 2. 打印请求基本信息
        LOG.info("------------- 开始 -------------");
        LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
        LOG.info("类名方法: {}.{}", className, methodName);
        LOG.info("远程地址: {}", request.getRemoteAddr());

        // 3. 打印请求参数（排除 ServletRequest、ServletResponse、MultipartFile 等特殊类型）
        Object[] args = joinPoint.getArgs();
        Object[] arguments = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            // 排除无法序列化的特殊参数类型
            if (args[i] instanceof ServletRequest
                    || args[i] instanceof ServletResponse
                    || args[i] instanceof MultipartFile) {
                continue;
            }
            arguments[i] = args[i];
        }

        // 排除敏感字段（如密码、身份证号等，此处为空，可根据需求添加）
        String[] excludeProperties = {};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludeFilter = filters.addFilter();
        excludeFilter.addExcludes(excludeProperties);

        // 打印处理后的请求参数（JSON格式）
        LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludeFilter));
    }

    /**
     * 环绕通知：在 Controller 方法执行前后执行，打印返回结果及方法耗时
     * @param proceedingJoinPoint 可执行连接点：支持执行目标方法（proceed()）
     * @return 目标方法的返回结果（需原样返回，否则会影响接口响应）
     * @throws Throwable 目标方法执行过程中抛出的异常
     */
    @Around("controllerPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 1. 记录方法开始执行时间
        long startTime = System.currentTimeMillis();

        // 2. 执行目标 Controller 方法，获取返回结果
        Object result = proceedingJoinPoint.proceed();

        // 3. 打印返回结果（排除敏感字段）
        String[] excludeProperties = {};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludeFilter = filters.addFilter();
        excludeFilter.addExcludes(excludeProperties);
        LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludeFilter));

        // 4. 计算并打印方法执行耗时
        LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);

        // 5. 返回目标方法结果（必须返回，否则前端无法获取响应）
        return result;
    }
}
